package com.abel.bigwater.tlv.model

import com.alibaba.fastjson.annotation.JSONField
import io.netty.buffer.ByteBuf
import io.netty.buffer.Unpooled
import kotlin.experimental.and

data class TlvDataPiece(var tag: Short = 0,
                        var id: Short = 0,
                        var value: ByteArray? = null) : TlvDataApi {
    @JSONField(serializeUsing = TlvExceptionSerializer::class)
    var parseResult: TlvException? = null

    val hexString: String?
        get() = if (value != null) TlvHelper.toHexString(value!!) else ""

    fun writeByte(b: Byte) {
        value = ByteArray(1).apply { this[0] = b }
    }

    fun writeShort(s: Short) {
        value = value ?: ByteArray(2)
        Unpooled.buffer().writeShort(s.toInt()).readBytes(value)
    }

    fun writeShortLE(s: Short) {
        value = value ?: ByteArray(2)
        Unpooled.buffer().writeShortLE(s.toInt()).readBytes(value)
    }

    fun writeInt(s: Int) {
        value = value ?: ByteArray(4)
        Unpooled.buffer().writeInt(s).readBytes(value)
    }

    fun writeIntLE(s: Int) {
        value = value ?: ByteArray(4)
        Unpooled.buffer().writeIntLE(s).readBytes(value)
    }

    fun readByte(): Short {
        return Unpooled.buffer().writeBytes(value).readByte().toShort() and 0xFF
    }

    fun readShort(): Int {
        return Unpooled.buffer().writeBytes(value).readShort().toInt() and 0xFFFF
    }

    fun readShortLE(): Int {
        return Unpooled.buffer().writeBytes(value).readShortLE().toInt() and 0xFFFF
    }

    fun readInt(): Long {
        return Unpooled.buffer().writeBytes(value).readInt().toLong() and 0xFFFFFFFF
    }

    fun readIntLE(): Long {
        return Unpooled.buffer().writeBytes(value).readIntLE().toLong() and 0xFFFFFFFF
    }

    fun readBcdString(): String {
        return TlvHelper.toBcdString(value!!)
    }

    override fun finalLen(): Int {
        when (tag) {
            BaseParam.TAG -> {
                val p1 = BaseParam.valueMap[id]?.len ?: throw TlvUnkownTagException("${tag}/${id}")
                return p1 + 1
            }

            RealtimeParam.TAG -> {
                val p1 = RealtimeParam.valueMap[id]?.len ?: throw TlvUnkownTagException("${tag}/${id}")
                return p1 + 1
            }

            TerminalParam.TAG -> {
                val p1 = TerminalParam.valueMap[id]?.len ?: throw TlvUnkownTagException("${tag}/${id}")
                return p1 + 1
            }

            WarnParam.TAG -> {
                val p1 = WarnParam.valueMap[id]?.len ?: throw TlvUnkownTagException("${tag}/${id}")
                return p1 + 1
            }
        }

        throw TlvUnkownTagException("${tag}/${id}")
    }

    override fun fill(buf: ByteBuf): ByteBuf {
        with(buf.alloc().buffer()) {
            if (value?.size ?: 0 < finalLen() - 1) throw TlvInvalidException("invalid size: ${value?.size}")

            writeByte(id.toInt())
            writeBytes(value?.copyOf(finalLen() - 1))

            // write in whole
            buf.writeBytes(this)
        }

        return buf
    }

    override fun parse(buf: ByteBuf): ByteBuf {
        id = buf.readByte().toShort()
        when (tag) {
            BaseParam.TAG -> {
                val p1 = BaseParam.valueMap[id]?.len ?: throw TlvUnkownTagException("${tag}/${id}")
                if (buf.readableBytes() < p1) throw TlvInvalidException("${buf.readableBytes()}<${p1}")

                value = ByteArray(p1.toInt())
                buf.readBytes(value)
            }

            RealtimeParam.TAG -> {
                val p1 = RealtimeParam.valueMap[id]?.len ?: throw TlvUnkownTagException("${tag}/${id}")
                if (buf.readableBytes() < p1) throw TlvInvalidException("${buf.readableBytes()}<${p1}")

                value = ByteArray(p1.toInt())
                buf.readBytes(value)
            }

            TerminalParam.TAG -> {
                val p1 = TerminalParam.valueMap[id]?.len ?: throw TlvUnkownTagException("${tag}/${id}")
                if (buf.readableBytes() < p1) throw TlvInvalidException("${buf.readableBytes()}<${p1}")

                value = ByteArray(p1.toInt())
                buf.readBytes(value)
            }

            WarnParam.TAG -> {
                val p1 = WarnParam.valueMap[id]?.len ?: throw TlvUnkownTagException("${tag}/${id}")
                if (buf.readableBytes() < p1) throw TlvInvalidException("${buf.readableBytes()}<${p1}")

                value = ByteArray(p1.toInt())
                buf.readBytes(value)
            }
        }

        return buf
    }

}
